package com.aizuda.encrypt.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.StrUtil;
import com.aizuda.encrypt.entity.ColumnRule;
import com.aizuda.encrypt.entity.TableRule;
import com.aizuda.encrypt.service.ReadRemoteConfigService;
import com.aizuda.encrypt.util.RuleUtil;
import com.google.common.base.Function;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Apollo远程配置服务读取实现
 *
 * @author nn200433
 * @date 2024-03-27 11:01:49
 */
@Slf4j
public class ApolloReadRemoteConfigServiceImpl implements ReadRemoteConfigService {

    private static final String CONFIG_SERVICE            = "com.ctrip.framework.apollo.ConfigService";
    private static final String NAME_SPACE_CONFIG_SERVICE = "com.ctrip.framework.apollo.Config";
    private static final String MAVEN_CONFIG              = "<dependency>\n" +
            "    <groupId>com.ctrip.framework.apollo</groupId>\n" +
            "    <artifactId>apollo-client</artifactId>\n" +
            "    <exclusions>\n" +
            "        <exclusion>\n" +
            "            <groupId>com.google.guava</groupId>\n" +
            "            <artifactId>guava</artifactId>\n" +
            "        </exclusion>\n" +
            "    </exclusions>\n" +
            "</dependency>";

    @Override
    public Map<String, TableRule> getCfg(Environment env) {
        // 读取apollo的命名空间
        final String namespaces = env.getProperty("apollo.bootstrap.namespaces");
        // 循环获取配置
        final List<String>                   namespaceList = StrUtil.split(namespaces, StrUtil.COMMA, Boolean.TRUE, Boolean.TRUE);
        Map<String, Map<String, ColumnRule>> waitBuildMap  = new HashMap<String, Map<String, ColumnRule>>();
        for (final String s : namespaceList) {
            final Set<String> propertyNames = getPropertyNames(s);
            propertyNames.forEach(key -> RuleUtil.findTableRule(key, getPropertyStr(s, key), waitBuildMap));
        }
        Map<String, TableRule> resultMap = new HashMap<String, TableRule>();
        if (CollUtil.isNotEmpty(waitBuildMap)) {
            for (final Map.Entry<String, Map<String, ColumnRule>> entry : waitBuildMap.entrySet()) {
                final String                  table         = entry.getKey();
                final Map<String, ColumnRule> columnRuleMap = entry.getValue();
                resultMap.put(table, new TableRule(columnRuleMap));
            }
            log.debug("---> [读取配置] 构造结果：{}", resultMap);
        }
        return resultMap;
    }

    /**
     * 获取命名空间下的配置
     *
     * @param namespace 命名空间
     * @param key       键
     * @return {@link String }
     * @author nn200433
     */
    private String getPropertyStr(String namespace, String key) {
        return getProperty(namespace, key, Convert::toStr, StrUtil.EMPTY);
    }

    /**
     * 获取命名空间下的配置
     *
     * @param namespace    命名空间
     * @param key          key
     * @param function     value类型转换器
     * @param defaultValue 默认值
     * @return {@link T }
     * @author nn200433
     */
    private <T> T getProperty(String namespace, String key, Function<String, T> function, T defaultValue) {
        T            o                 = defaultValue;
        final Object configByNamespace = getConfigByNamespace(namespace);
        if (null == configByNamespace) {
            return o;
        }
        try {
            final Class<?> clz         = configByNamespace.getClass();
            final Method   getProperty = clz.getMethod("getProperty", String.class, Function.class, Object.class);
            o = (T) getProperty.invoke(configByNamespace, key, function, defaultValue);
        } catch (Exception e) {
            log.error("", e);
        }
        return o;
    }

    /**
     * 获取命名空间下的所有配置key
     *
     * @param namespace 命名空间
     * @return {@link Set }<{@link String }>
     * @author nn200433
     */
    private Set<String> getPropertyNames(String namespace) {
        Set<String>  propertyNames     = new HashSet<String>();
        final Object configByNamespace = getConfigByNamespace(namespace);
        if (null == configByNamespace) {
            return propertyNames;
        }
        try {
            final Class<?> clz              = configByNamespace.getClass();
            final Method   getPropertyNames = clz.getMethod("getPropertyNames");
            propertyNames = Convert.convert(new TypeReference<Set<String>>() {}, getPropertyNames.invoke(configByNamespace));
        } catch (Exception e) {
            log.error("", e);
        }
        return propertyNames;
    }

    /**
     * 通过命名空间获取配置对象
     *
     * @param namespace 命名空间
     * @return {@link Object }
     * @author nn200433
     */
    private Object getConfigByNamespace(String namespace) {
        final Object configService = getConfigService();
        if (null == configService) {
            return null;
        }
        return Singleton.get(NAME_SPACE_CONFIG_SERVICE + StrUtil.DOT + namespace, () -> {
            Object o = null;
            try {
                final Class<?> clz       = configService.getClass();
                final Method   getConfig = clz.getMethod("getConfig", String.class);
                o = getConfig.invoke(configService, namespace);
            } catch (Exception e) {
                log.error("", e);
            }
            return o;
        });
    }

    /**
     * 获取配置服务
     *
     * @return {@link Object }
     * @author nn200433
     */
    private Object getConfigService() {
        return Singleton.get(CONFIG_SERVICE, () -> {
            Object o = null;
            try {
                Class<?> clz = Class.forName("com.ctrip.framework.apollo.ConfigService");
                o = clz.getDeclaredConstructor().newInstance();
            } catch (ClassNotFoundException e) {
                log.error("请检查是否引入 Apollo 客户端！！！示例配置如下：\n{}", MAVEN_CONFIG);
            } catch (Exception e) {
                log.error("", e);
            }
            return o;
        });
    }

}
